﻿using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Windows.Input;
#if SILVERLIGHT
using System.Windows;
#else
using Windows.UI.Xaml;
#endif

namespace Microsoft.PlayerFramework
{
    /// <summary>
    /// Class used to help bind Xaml to an IInteractiveViewModel
    /// </summary>
    public class ViewModelCommands : FrameworkElement, INotifyPropertyChanged
    {
        /// <summary>
        /// Creates a new instance of ViewModelCommands
        /// </summary>
        public ViewModelCommands()
        {
            InitializeCommands();
        }

        DelegateCommand playResumeCommand;
        DelegateCommand pauseCommand;
        DelegateCommand<bool?> playPauseCommand;
        DelegateCommand stopCommand;
        DelegateCommand replayCommand;
        DelegateCommand audioSelectionCommand;
        DelegateCommand rewindCommand;
        DelegateCommand fastForwardCommand;
        DelegateCommand<double> volumeCommand;
        DelegateCommand<bool> muteCommand;
        DelegateCommand<bool> slowMotionCommand;
        DelegateCommand captionsCommand;
        DelegateCommand<bool> fullScreenCommand;
#if SILVERLIGHT
        DelegateCommand displayModeCommand;
#endif
        DelegateCommand<TimeSpan> seekCommand;
        DelegateCommand<VisualMarker> skipPreviousCommand;
        DelegateCommand<VisualMarker> skipNextCommand;
        DelegateCommand<TimeSpan> skipBackCommand;
        DelegateCommand<TimeSpan> skipAheadCommand;
        DelegateCommand goLiveCommand;

        /// <summary>
        /// The command used to control the PlayResume action.
        /// </summary>
        public ICommand PlayResumeCommand { get { return playResumeCommand; } }

        /// <summary>
        /// The command used to control the pause action.
        /// </summary>
        public ICommand PauseCommand { get { return pauseCommand; } }

        /// <summary>
        /// The command used to control the play/pause toggle action.
        /// </summary>
        public ICommand PlayPauseCommand { get { return playPauseCommand; } }

        /// <summary>
        /// The command used to control the stop action.
        /// </summary>
        public ICommand StopCommand { get { return stopCommand; } }

        /// <summary>
        /// The command used to control the replay (instant replay) action.
        /// </summary>
        public ICommand ReplayCommand { get { return replayCommand; } }

        /// <summary>
        /// The command used to control the audio stream selection action.
        /// </summary>
        public ICommand AudioSelectionCommand { get { return audioSelectionCommand; } }

        /// <summary>
        /// The command used to control the rewind action.
        /// </summary>
        public ICommand RewindCommand { get { return rewindCommand; } }

        /// <summary>
        /// The command used to control the fast forward action.
        /// </summary>
        public ICommand FastForwardCommand { get { return fastForwardCommand; } }

        /// <summary>
        /// The command used to control the volume action.
        /// </summary>
        public ICommand VolumeCommand { get { return volumeCommand; } }

        /// <summary>
        /// The command used to control the mute action.
        /// </summary>
        public ICommand MuteCommand { get { return muteCommand; } }

        /// <summary>
        /// The command used to control the slow motion action.
        /// </summary>
        public ICommand SlowMotionCommand { get { return slowMotionCommand; } }

        /// <summary>
        /// The command used to control the captiosn action.
        /// </summary>
        public ICommand CaptionsCommand { get { return captionsCommand; } }

        /// <summary>
        /// The command used to control the full screen action.
        /// </summary>
        public ICommand FullScreenCommand { get { return fullScreenCommand; } }

#if SILVERLIGHT
        /// <summary>
        /// The command used to control the action to cycle the display mode (letterbox, stretched, ...etc).
        /// </summary>
        public ICommand DisplayModeCommand { get { return displayModeCommand; } }
#endif

        /// <summary>
        /// The command used to control the seek action.
        /// </summary>
        public ICommand SeekCommand { get { return seekCommand; } }

        /// <summary>
        /// The command used to control the skip to the previous marker.
        /// </summary>
        public ICommand SkipPreviousCommand { get { return skipPreviousCommand; } }

        /// <summary>
        /// The command used to control the skip next marker.
        /// </summary>
        public ICommand SkipNextCommand { get { return skipNextCommand; } }

        /// <summary>
        /// The command used to control the skip back (usually 30 seconds) action.
        /// </summary>
        public ICommand SkipBackCommand { get { return skipBackCommand; } }

        /// <summary>
        /// The command used to control the skip ahead (usually 30 seconds) action.
        /// </summary>
        public ICommand SkipAheadCommand { get { return skipAheadCommand; } }

        /// <summary>
        /// The command used to control the go live action.
        /// </summary>
        public ICommand GoLiveCommand { get { return goLiveCommand; } }

        void InitializeCommands()
        {
            playResumeCommand = new DelegateCommand(() => ViewModel.PlayResume(), () => ViewModel == null ? false : ViewModel.IsPlayResumeEnabled);
            pauseCommand = new DelegateCommand(() => ViewModel.Pause(), () => ViewModel == null ? false : ViewModel.IsPauseEnabled);
            playPauseCommand = new DelegateCommand<bool?>(isPaused =>
            {
                if (isPaused.GetValueOrDefault(true))
                {
                    ViewModel.PlayResume();
                }
                else
                {
                    ViewModel.Pause();
                }
            }, () => ViewModel == null ? false : ViewModel.IsPauseEnabled || ViewModel.IsPlayResumeEnabled);
            stopCommand = new DelegateCommand(() => ViewModel.Stop(), () => ViewModel == null ? false : ViewModel.IsStopEnabled);
            replayCommand = new DelegateCommand(() => ViewModel.Replay(), () => ViewModel == null ? false : ViewModel.IsReplayEnabled);
            rewindCommand = new DelegateCommand(() => ViewModel.DecreasePlaybackRate(), () => ViewModel == null ? false : ViewModel.IsRewindEnabled);
            fastForwardCommand = new DelegateCommand(() => ViewModel.IncreasePlaybackRate(), () => ViewModel == null ? false : ViewModel.IsFastForwardEnabled);
            slowMotionCommand = new DelegateCommand<bool>(v => ViewModel.SetSlowMotion(v), () => ViewModel == null ? false : ViewModel.IsSlowMotionEnabled);
            volumeCommand = new DelegateCommand<double>(v => ViewModel.SetVolume(v), () => ViewModel != null);
            muteCommand = new DelegateCommand<bool>(v => ViewModel.SetMuted(v), () => ViewModel != null);
            captionsCommand = new DelegateCommand(() => ViewModel.InvokeCaptions(), () => ViewModel == null ? false : ViewModel.IsCaptionsEnabled);
            audioSelectionCommand = new DelegateCommand(() => ViewModel.InvokeAudioSelection(), () => ViewModel != null);
            fullScreenCommand = new DelegateCommand<bool>(v => ViewModel.SetFullScreen(v), () => ViewModel != null);
            seekCommand = new DelegateCommand<TimeSpan>(v => ViewModel.Seek(v), () => ViewModel == null ? false : ViewModel.IsSeekEnabled);
            skipPreviousCommand = new DelegateCommand<VisualMarker>(v => ViewModel.SkipPrevious(), () => ViewModel == null ? false : ViewModel.IsSkipPreviousEnabled);
            skipNextCommand = new DelegateCommand<VisualMarker>(v => ViewModel.SkipNext(), () => ViewModel == null ? false : ViewModel.IsSkipNextEnabled);
            skipBackCommand = new DelegateCommand<TimeSpan>(v => ViewModel.SkipBack(), () => ViewModel == null ? false : ViewModel.IsSkipBackEnabled);
            skipAheadCommand = new DelegateCommand<TimeSpan>(v => ViewModel.SkipAhead(), () => ViewModel == null ? false : ViewModel.IsSkipAheadEnabled);
            goLiveCommand = new DelegateCommand<bool>(v => ViewModel.GoLive(), () => ViewModel == null ? false : ViewModel.IsGoLiveEnabled);
#if SILVERLIGHT
            displayModeCommand = new DelegateCommand(() => ViewModel.CycleDisplayMode(), () => ViewModel != null);
#endif
        }

        #region ViewModel
        /// <summary>
        /// Identifies the ViewModel dependency property.
        /// </summary>
        public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(IInteractiveViewModel), typeof(ViewModelCommands), new PropertyMetadata(null, (d, e) => ((ViewModelCommands)d).OnViewModelChanged(e.OldValue as IInteractiveViewModel, e.NewValue as IInteractiveViewModel)));

        void OnViewModelChanged(IInteractiveViewModel oldValue, IInteractiveViewModel newValue)
        {
            if (oldValue != null)
            {
                oldValue.IsPlayResumeEnabledChanged -= playResumeCommand.OnCanExecuteChanged;
                oldValue.IsPauseEnabledChanged -= pauseCommand.OnCanExecuteChanged;
                oldValue.IsPlayResumeEnabledChanged -= playPauseCommand.OnCanExecuteChanged;
                oldValue.IsPauseEnabledChanged -= playPauseCommand.OnCanExecuteChanged;
                oldValue.IsStopEnabledChanged -= stopCommand.OnCanExecuteChanged;
                oldValue.IsReplayEnabledChanged -= replayCommand.OnCanExecuteChanged;
                oldValue.IsAudioSelectionEnabledChanged -= audioSelectionCommand.OnCanExecuteChanged;
                oldValue.IsCaptionsEnabledChanged -= captionsCommand.OnCanExecuteChanged;
                oldValue.IsRewindEnabledChanged -= rewindCommand.OnCanExecuteChanged;
                oldValue.IsFastForwardEnabledChanged -= fastForwardCommand.OnCanExecuteChanged;
                oldValue.IsSlowMotionEnabledChanged -= slowMotionCommand.OnCanExecuteChanged;
                oldValue.IsSeekEnabledChanged -= seekCommand.OnCanExecuteChanged;
                oldValue.IsSkipPreviousEnabledChanged -= skipPreviousCommand.OnCanExecuteChanged;
                oldValue.IsSkipNextEnabledChanged -= skipNextCommand.OnCanExecuteChanged;
                oldValue.IsSkipBackEnabledChanged -= skipBackCommand.OnCanExecuteChanged;
                oldValue.IsSkipAheadEnabledChanged -= skipAheadCommand.OnCanExecuteChanged;
                oldValue.IsGoLiveEnabledChanged -= goLiveCommand.OnCanExecuteChanged;
                oldValue.IsPlayResumeEnabledChanged -= vm_IsPlayResumeEnabledChanged;
            }

            if (newValue != null)
            {
                newValue.IsPlayResumeEnabledChanged += playResumeCommand.OnCanExecuteChanged;
                newValue.IsPauseEnabledChanged += pauseCommand.OnCanExecuteChanged;
                newValue.IsPlayResumeEnabledChanged += playPauseCommand.OnCanExecuteChanged;
                newValue.IsPauseEnabledChanged += playPauseCommand.OnCanExecuteChanged;
                newValue.IsStopEnabledChanged += stopCommand.OnCanExecuteChanged;
                newValue.IsReplayEnabledChanged += replayCommand.OnCanExecuteChanged;
                newValue.IsAudioSelectionEnabledChanged += audioSelectionCommand.OnCanExecuteChanged;
                newValue.IsCaptionsEnabledChanged += captionsCommand.OnCanExecuteChanged;
                newValue.IsRewindEnabledChanged += rewindCommand.OnCanExecuteChanged;
                newValue.IsFastForwardEnabledChanged += fastForwardCommand.OnCanExecuteChanged;
                newValue.IsSlowMotionEnabledChanged += slowMotionCommand.OnCanExecuteChanged;
                newValue.IsSeekEnabledChanged += seekCommand.OnCanExecuteChanged;
                newValue.IsSkipPreviousEnabledChanged += skipPreviousCommand.OnCanExecuteChanged;
                newValue.IsSkipNextEnabledChanged += skipNextCommand.OnCanExecuteChanged;
                newValue.IsSkipBackEnabledChanged += skipBackCommand.OnCanExecuteChanged;
                newValue.IsSkipAheadEnabledChanged += skipAheadCommand.OnCanExecuteChanged;
                newValue.IsGoLiveEnabledChanged += goLiveCommand.OnCanExecuteChanged;
                newValue.IsPlayResumeEnabledChanged += vm_IsPlayResumeEnabledChanged;
            }

            //PlayPauseCommand.OnCanExecuteChanged();    // this will get triggered by the PlayResume and pause command changes
            playResumeCommand.OnCanExecuteChanged();
            pauseCommand.OnCanExecuteChanged();
            stopCommand.OnCanExecuteChanged();
            replayCommand.OnCanExecuteChanged();
            audioSelectionCommand.OnCanExecuteChanged();
            captionsCommand.OnCanExecuteChanged();
            rewindCommand.OnCanExecuteChanged();
            fastForwardCommand.OnCanExecuteChanged();
            volumeCommand.OnCanExecuteChanged();
            muteCommand.OnCanExecuteChanged();
            slowMotionCommand.OnCanExecuteChanged();
            captionsCommand.OnCanExecuteChanged();
            fullScreenCommand.OnCanExecuteChanged();
            seekCommand.OnCanExecuteChanged();
            skipPreviousCommand.OnCanExecuteChanged();
            skipNextCommand.OnCanExecuteChanged();
            skipBackCommand.OnCanExecuteChanged();
            skipAheadCommand.OnCanExecuteChanged();
            goLiveCommand.OnCanExecuteChanged();
            OnPropertyChanged(() => FavorPlayResume);
#if SILVERLIGHT
            displayModeCommand.OnCanExecuteChanged();
#endif

            if (ViewModelChanged != null) ViewModelChanged(this, EventArgs.Empty);
        }

        /// <summary>
        /// Occurs when the ViewModel property changes.
        /// </summary>
        public event EventHandler ViewModelChanged;

        /// <summary>
        /// Gets or sets if the command used for databinding with user interactive features.
        /// You can set this property to create custom behaviors from plugins or your application.
        /// </summary>
        public IInteractiveViewModel ViewModel
        {
            get { return (IInteractiveViewModel)GetValue(ViewModelProperty); }
            set { SetValue(ViewModelProperty, value); }
        }
        #endregion

        #region FavorPlayResume

        /// <summary>
        /// Gets whether the play/PlayResume action could be used. Sometimes both the PlayResume and Pause commands can make sense such as when fast forwarding.
        /// </summary>
        public bool FavorPlayResume
        {
            get { return ViewModel != null ? ViewModel.IsPlayResumeEnabled : false; }
            set { /* do nothing, this is here just so we can get the binding to force updates if the state refires with the same status after clicking */ }
        }

        void vm_IsPlayResumeEnabledChanged(object sender, RoutedEventArgs e)
        {
            OnPropertyChanged(() => FavorPlayResume);
        }
        #endregion

        #region PropertyChanged

        /// <inheritdoc /> 
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
        }

        void OnPropertyChanged<T>(Expression<Func<T>> property)
        {
            OnPropertyChanged(GetPropertyName(property));
        }

        static string GetPropertyName<T>(Expression<Func<T>> property)
        {
            return (property.Body as MemberExpression).Member.Name;
        }

        #endregion
    }
}
